package com.stargis.ftpdown.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPCmd;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


@Component
@Slf4j
public class FTPUtil {

    /** 本地字符编码 */
    private static String LOCAL_CHARSET ="GBK";
    /** ftp服务器字符编码 */
    public static String REMOTE_CHARSET = "ISO-8859-1";


    /**
     * 创建远程目录
     * @param pathName 远程路径
     * @param ftpClient ftp客户端
     * @param ftpRoot ftp连接根目录
     * @return 创建结果
     */
    public boolean createDirectory(String pathName,FTPClient ftpClient,String ftpRoot){
        boolean result = false;
        try {
            String[] dirs=pathName.split("/");
            String curDirPath="";
            for (String dir : dirs) {
                curDirPath += dir + "/";
                result = false;
                if (isDirectoryExists(curDirPath, ftpClient, ftpRoot)) {
                    continue;
                } else {
                    String encodedCurDirPath= encodeRemotePath(curDirPath,ftpClient);
                    result = ftpClient.makeDirectory(encodedCurDirPath);
                }
                if (!result) {
                    log.error("创建远程目录[{}]失败", curDirPath);
                    return false;
                }
            }
        } catch (Exception e) {
            log.error("创建远程目录["+ pathName +"]失败：", e.getMessage());
        }
        return result;
    }
    /**
     * 移动文件或文件夹到新路径
     * @param fromPath 原路径
     * @param toPath  新路径
     * @param ftpClient ftp客户端
     * @return 移动结果
     */
    public boolean moveFileOrDir(String fromPath,String toPath,FTPClient ftpClient,String ftpRoot){
        boolean result = false;
        try {
            fromPath=normalPath(fromPath);
            toPath=normalPath(toPath);
            String encodedFromPath=encodeRemotePath(fromPath,ftpClient);
            String encodedToPath=encodeRemotePath(toPath,ftpClient);
            String parentToPath=toPath.substring(0,toPath.lastIndexOf("/"));
            if(!isDirectoryExists(parentToPath,ftpClient,ftpRoot))
            {
                createDirectory(parentToPath,ftpClient,ftpRoot);
            }
            int RNFR = ftpClient.sendCommand(FTPCmd.RNFR,encodedFromPath);
            if(RNFR>350){
                log.error("移动文件或文件夹["+ fromPath +"]失败：原路径不存在！");
                return false;
            }
            int RNTO = ftpClient.sendCommand(FTPCmd.RNTO,encodedToPath);
            if(RNTO<300) {
                result = true;
            }else{
                log.error("移动文件或文件夹["+ fromPath +"]失败：执行移动命令失败！");
                return false;
            }
        } catch (Exception e) {
            log.error("移动文件或文件夹["+ fromPath +"]失败：", e.getMessage());
        }
        return result;
    }

    /**
     * 删除远程目录
     * @param path 远程目录路径
     * @param ftpClient ftp连接
     * @return 删除结果
     */
    public boolean removeDirectory(String path,FTPClient ftpClient) {
        boolean result = false;
        try {
            String encodedPath = encodeRemotePath(path,ftpClient);
            result = ftpClient.removeDirectory(encodedPath);
        } catch (Exception e) {
            log.error("删除远程目录["+ path + "]失败：", e.getMessage());
        }
        return result;
    }

    /**
     * 删除远程目录，可递归删除所有
     * @param path 远程目录路径
     * @param isAll 是否递归删除
     * @return 删除结果
     */
    public boolean removeDirectory(String path, boolean isAll,FTPClient ftpClient) {
        boolean result = false;
        try {
            if (!isAll) {
                return removeDirectory(path,ftpClient);
            }
            String encodedPath= encodeRemotePath(path,ftpClient);
            FTPFile[] ftpFileArr = ftpClient.listFiles(encodedPath);
            if (ftpFileArr == null || ftpFileArr.length == 0) {
                return removeDirectory(path,ftpClient);
            }
            for (FTPFile ftpFile : ftpFileArr) {
                String name = ftpFile.getName();
                String decodedName= decodeRemotePath(name,ftpClient);
                if (ftpFile.isDirectory()) {
                   log.info("* [sD] 删除目录 [" + path + "/" + name + "]");
                   if (!ftpFile.getName().equals(".") && (!ftpFile.getName().equals(".."))) {
                        removeDirectory(path + "/" + name, true,ftpClient);
                    }
                } else if (ftpFile.isFile()) {
                    log.info("* [sF]删除文件 [" + path + "/" + name + "]");
                    deleteFile(path + "/" + name,ftpClient);
                }
            }
            result = ftpClient.removeDirectory(encodedPath);
        } catch (Exception e) {
            log.error("远程目录["+ path +"]删除失败：", e.getMessage());
        }
        return result;
    }

    /**
     * 查看目录是否存在
     *
     * @param path 查询目录
     * @param ftpClient ftp客户端
     * @param ftpRoot ftp根目录
     * @return 是否存在结果
     */
    public boolean isDirectoryExists(String path,FTPClient ftpClient,String ftpRoot) {
        boolean flag = false;
        try {
            String encodedPath= encodeRemotePath(path,ftpClient);
            String encodedRoot= encodeRemotePath(ftpRoot,ftpClient);
            ftpClient.changeWorkingDirectory(encodedPath);
            int returnCode = ftpClient.getReplyCode();
            if (returnCode == 550) {
                return false;
            }
            ftpClient.changeWorkingDirectory(encodedRoot);
            return true;
        } catch (Exception e) {
            log.error("ftp目录["+ path +"]是否存在查询失败!", e.getMessage());
        }
        return flag;
    }

      /**
     * 确定ftp服务器上指定文件是否存在
     * @param filePath 远程文件路径
     * @param ftpClient ftp连接
     * @return 文件存在返回true，不存在返回false
     */
    public boolean isFileExists(String filePath,FTPClient ftpClient)  {
        InputStream inputStream = null;
        try {
            String encodedPath=encodeRemotePath(filePath,ftpClient);
            inputStream = ftpClient.retrieveFileStream(encodedPath);
            int returnCode = ftpClient.getReplyCode();
            if (inputStream == null || returnCode == 550) {
                return false;
            }
            inputStream.close();
            ftpClient.completePendingCommand();
        }catch (Exception e){
            log.error("文件["+filePath+"]是否存在查询失败",e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 得到某个目录下的文件名列表
     *
     * @param path 目录路径
     * @param ftpClient ftp连接
     * @return 文件名列表
     */
    public List<String> getFileList(String path,FTPClient ftpClient) {
        List<String> retList = new ArrayList<>();
        try {
            String encodedPath=encodeRemotePath(path,ftpClient);
            FTPFile[] ftpFiles = ftpClient.listFiles(encodedPath);
            if (ftpFiles == null || ftpFiles.length == 0) {
                return retList;
            }
            for (FTPFile ftpFile : ftpFiles) {
                if (ftpFile.isFile()) {
                    retList.add(ftpFile.getName());
                }
            }
        } catch (Exception e) {
            log.error("目录下文件获取失败： ", e);
        }
        return retList;
    }


    /**
     * 删除文件
     *
     * @param pathName 文件路径
     * @return 文件删除结果
     */
    public boolean deleteFile(String pathName,FTPClient ftpClient) {
        boolean result = false;
        if(!isFileExists(pathName,ftpClient)){
            log.error("文件删除失败：文件["+pathName+"]不存在！");
            return false;
        }
        try {
            String encodedPath=encodeRemotePath(pathName,ftpClient);
            result = ftpClient.deleteFile(encodedPath);
        } catch (Exception e) {
            log.error("文件["+ pathName +"]删除失败: ", e.getMessage());
        }
        return result;
    }

    /**
     * 下载资源文件
     *
     * @param filePath 本地文件路径
     * @param ftpClient ftp连接
     * @return 下载结果
     * @throws IOException IO异常
     */
    public InputStream downFile(String filePath,FTPClient ftpClient) throws IOException {
        InputStream result  ;
        try {
            ftpClient.enterLocalPassiveMode();
            String encodedPath=encodeRemotePath(filePath,ftpClient);
            result = ftpClient.retrieveFileStream(encodedPath);
        } catch (Exception e) {
            log.error("文件["+filePath+"]下载失败 ", e.getMessage());
            throw e;
        }
        return result;
    }


    /**
     * 获取指定路径下的文件和文件夹
     *
     * @param path 目录路径
     * @param ftpClient ftp连接
     * @return 获取结果
     */
    public List<FTPFile> getFileAndDirectoryList(String path,FTPClient ftpClient,String ftpRoot) {
        List<FTPFile> list= new ArrayList<>();
        try {
            String encodedPath= encodeRemotePath(path,ftpClient);
            String encodedRoot= encodeRemotePath(ftpRoot,ftpClient);
            ftpClient.changeWorkingDirectory(encodedPath);
            FTPFile[] files = ftpClient.listFiles();
            if(files.length>0) {
                list = Arrays.asList(files);
            }
            ftpClient.changeWorkingDirectory(encodedRoot);
        } catch (Exception e) {
            log.error("获取目录["+ path +"]下文件和文件夹失败： ", e);
        }
        return list;
    }

    /***
     * 上传Ftp文件
     * @param localPath 本地文件路径
     * @param romotePath 上传服务器路径
     * @param ftpClient ftp客户端
     * @return 上传结果
     * */
    public boolean uploadFileNew(String localPath,String romotePath,FTPClient ftpClient,String ftpRoot) {
        File localFile=new File(localPath);
        BufferedInputStream inStream = null;

        boolean success = false;
        try {
            String encodeFtpRoot= encodeRemotePath(ftpRoot,ftpClient);
            ftpClient.changeWorkingDirectory(encodeFtpRoot);// 改变工作路径
            inStream = new BufferedInputStream(new FileInputStream(localFile));
            createDirectory(romotePath,ftpClient,ftpRoot);
            log.info("创建目录["+ romotePath +"].....");
            if(!romotePath.endsWith("/")){
                romotePath=romotePath+"/";
            }
            String curRemotePath = romotePath+localFile.getName();
            log.info(curRemotePath + "远程路径.....");
            String encodedFilePath = encodeRemotePath(curRemotePath,ftpClient);
            log.info(localFile.getName() + "开始上传.....");
            success = ftpClient.storeFile(encodedFilePath, inStream);
            if (success) {
                log.info(localFile.getName() + "上传成功");
            }
        }catch (IOException e) {
            e.printStackTrace();
            log.error(localFile + "上传失败:" + e.getMessage());
        }finally {
            if (inStream != null) {
                try {
                    inStream.close();
                }catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return success;
    }

    /***
     * 上传Ftp文件，指定目标文件名
     * @param localPath 本地文件路径
     * @param romotePath 上传服务器路径
     * @param ftpClient ftp客户端
     * @param remoteFileName 上传的目标文件名
     * @return 上传结果
     * */
    public boolean uploadFileNewWithFileName(String localPath,String romotePath,FTPClient ftpClient,String ftpRoot, String remoteFileName) {
        File localFile=new File(localPath);
        BufferedInputStream inStream = null;

        boolean success = false;
        try {
            String encodeFtpRoot= encodeRemotePath(ftpRoot,ftpClient);
            ftpClient.changeWorkingDirectory(encodeFtpRoot);// 改变工作路径
            inStream = new BufferedInputStream(new FileInputStream(localFile));
            createDirectory(romotePath,ftpClient,ftpRoot);
            log.info("创建目录["+ romotePath +"].....");
            if(!romotePath.endsWith("/")){
                romotePath=romotePath+"/";
            }
            String curRemotePath = romotePath + (StringUtils.isNotEmpty(remoteFileName) ? remoteFileName : localFile.getName());
            log.info(curRemotePath + "远程路径.....");
            String encodedFilePath = encodeRemotePath(curRemotePath,ftpClient);
            log.info(localFile.getName() + "开始上传.....");
            success = ftpClient.storeFile(encodedFilePath, inStream);
            if (success) {
                log.info(localFile.getName() + "上传成功");
            }
        }catch (IOException e) {
            e.printStackTrace();
            log.error(localFile + "上传失败:" + e.getMessage());
        }finally {
            if (inStream != null) {
                try {
                    inStream.close();
                }catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return success;
    }

    /**
     * Description: 从FTP服务器下载文件
     *
     * @param remotePath FTP服务器上的相对路径
     * @param localPath  下载后保存到本地的路径
     * @return 下载结果
     */
    public boolean downFile(String remotePath,String localPath,FTPClient ftpClient){
        boolean result = false;
        OutputStream is=null;
        try {
            String encodedPath=encodeRemotePath(remotePath,ftpClient);
            String normalLocalPath=normalPath(localPath);
            String localParent=normalLocalPath.substring(0,normalLocalPath.lastIndexOf("/"));
            File localDir=new File(localParent);
            if(!localDir.isDirectory()){
                localDir.mkdirs();
            }
            File localFile = new File(localPath);
            is = new FileOutputStream(localFile);
            result=ftpClient.retrieveFile(encodedPath, is);
            int reply_code=ftpClient.getReplyCode();
            if(reply_code==550){
                log.error("文件["+remotePath+"]下载失败：要下载的文件不存在！");
            }
            ftpClient.sendCommand("pwd");
        }catch (Exception e){
            e.printStackTrace();
            log.error("文件["+remotePath+"]下载失败："+e.getMessage());
        }finally {
            try {
                is.flush();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 将路径中的‘\’改为‘/’
     * @param oriPath 原路径
     * @return 规划路径
     */
    public static String normalPath(String oriPath){
        if( StringUtils.isBlank(oriPath)){
            return "";
        }
        return oriPath.replaceAll("\\\\","/");
    }

    /**
     * 将ftp远程路径编码为ISO_8859_1格式
     * @param oriPath 远程路径
     * @param ftpClient ftp连接
     * @return 编码后路径
     * @throws UnsupportedEncodingException
     */
    public static String encodeRemotePath(String oriPath,FTPClient ftpClient) throws UnsupportedEncodingException {
        if( StringUtils.isBlank(oriPath)){
            return "";
        }
        return new String(oriPath.getBytes(ftpClient.getControlEncoding()),REMOTE_CHARSET);
    }

    /**
     * 将ftp远程路径(ISO_9959_1)编码为本地编码格式（UTF-8,GBK)
     * @param remotePath 远程路径
     * @param ftpClient ftp连接
     * @return 编码后路径
     * @throws UnsupportedEncodingException
     */
    public static String decodeRemotePath(String remotePath,FTPClient ftpClient) throws UnsupportedEncodingException {
        if( StringUtils.isBlank(remotePath)){
            return "";
        }
        return new String(remotePath.getBytes(REMOTE_CHARSET),ftpClient.getControlEncoding());
    }

    /**
     * 读取流
     *
     * @param inStream
     * @return 字节数组
     * @throws Exception
     */
    public static byte[] readStream(InputStream inStream,FTPClient ftpClient) throws Exception {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        byte[] rst = null;
        int len = -1;
        try {
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            rst = outSteam.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (null != outSteam) {
                    outSteam.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            try {
                if (null != inStream) {
                    inStream.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return rst;
    }
    /**
     * @TODO: 关闭ftp连接
     */
    public static void disconnect(FTPClient ftpClient) {
        if (null != ftpClient && ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException ex) {
                // do nothing
                ex.printStackTrace();
            }
        }
    }


    /**
     * 下载ftp文件夹
     *
     * @param remotePath 远程目录
     * @param localPath 本地存放目录
     * @param ftpClient ftp连接
     * @param ftpRoot ftp服务器根目录
     * @param relativePath 当前文件夹相对路径
     * @return 获取结果
     */
    public boolean downloadDir(String remotePath,String localPath,FTPClient ftpClient,String ftpRoot,String relativePath) {
        boolean result= true;
        try {
            String encodedRoot= encodeRemotePath(ftpRoot,ftpClient);
            String curLocalPath=localPath+"/"+relativePath;
            String curRemotePath=remotePath+"/"+relativePath;
            if(StringUtils.isBlank(relativePath)){
                curLocalPath= localPath;
                curRemotePath= remotePath;
            }
            String encodedCurRemotePath= encodeRemotePath(curRemotePath,ftpClient);
            ftpClient.changeWorkingDirectory(encodedRoot);
            ftpClient.changeWorkingDirectory(encodedCurRemotePath);
            int returnCode = ftpClient.getReplyCode();
            if (returnCode == 550) {
                log.error("下载文件夹["+ remotePath +"]失败：文件夹不存在!");
                ftpClient.changeWorkingDirectory(encodedRoot);
                return false;
            }
            //创建本地文件夹
            File curDir=new File(curLocalPath);
            if(curDir.exists()  && curDir.isDirectory()){
            }else{
                result = curDir.mkdirs();
                if(!result){
                    log.error("创建本地文件夹["+ curDir +"]失败");
                    ftpClient.changeWorkingDirectory(encodedRoot);
                    return false;
                }
            }
            FTPFile[] files = ftpClient.listFiles();
            for (FTPFile file : files) {
                if (file.isFile()) {
                    String filePath= remotePath+"/"+ relativePath + "/"+file.getName();
                    String fileLocalPath=localPath+"/"+relativePath+"/"+file.getName();
                    if(StringUtils.isBlank(relativePath)){
                        filePath= remotePath+"/"+file.getName();
                        fileLocalPath=localPath+"/"+file.getName();
                    }
                    ftpClient.changeWorkingDirectory(encodedRoot);
                    result = downFile(filePath,fileLocalPath,ftpClient);
                    if(!result) {
                        log.error("下载文件["+ filePath +"]失败：");
                        ftpClient.changeWorkingDirectory(encodedRoot);
                        return false;
                    }
                }
            }
            for (FTPFile file : files) {
                if (file.isDirectory()) {
                    String subRelativePath =  relativePath + "/"+file.getName();
                    if(StringUtils.isBlank(relativePath)){
                        subRelativePath=  file.getName();
                    }
                    String dirPath=localPath+"/"+subRelativePath;
                    result = downloadDir(remotePath,localPath,ftpClient,ftpRoot,subRelativePath);
                    if(!result) {
                        log.error("下载文件夹["+ dirPath +"]失败：");
                        ftpClient.changeWorkingDirectory(encodedRoot);
                        return false;
                    }
                }
            }
            ftpClient.changeWorkingDirectory(encodedRoot);
        } catch (Exception e) {
            log.error("下载目录["+ remotePath +"]下文件和文件夹失败："+ e.getMessage());
        }
        return result;
    }

}
